﻿using System.Collections.Generic;
using System.IO;

namespace Kivensoft.XlsWriter {

    public class Workbook {
        /// <summary>
        /// 当前选择的工作表
        /// </summary>
		public int SelectedWorksheet { get; private set; }

        /// <summary>
        /// 获取当前工作表
        /// </summary>
		public Worksheet CurrentWorksheet { get; private set; }

        /// <summary>
        /// 所有工作表
        /// </summary>
		public List<Worksheet> Worksheets { get; private set; }

        /// <summary>
        /// 无参数保存时的文件名
        /// </summary>
		public string FileName { get; set; }

        /// <summary>
        /// 工作簿中的所有样式
        /// </summary>
		public List<Style> Styles { get; private set; }

        /// <summary>
        /// 工作薄相关信息，创建时间，作者之类的
        /// </summary>
		public Metadata WorkbookMetadata { get; set; }


        /// <summary>
        /// 工作薄是否保护标志
        /// </summary>
        public bool UseWorkbookProtection { get; set; }

        /// <summary>
        /// 密码
        /// </summary>
        /// <see cref="SetWorkbookProtection"/>
		public string WorkbookProtectionPassword { get; private set; }

        /// <summary>
        /// 窗口锁定标志
        /// </summary>
        /// <see cref="SetWorkbookProtection"/> 
		public bool LockWindowsIfProtected { get; private set; }

        /// <summary>
        /// 结构保护标志
        /// </summary>
        /// <see cref="SetWorkbookProtection"/>
		public bool LockStructureIfProtected { get; private set; }
        
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="createWorkSheet">为真时创建缺省的工作表</param>
        public Workbook(bool createWorkSheet) { 
            Worksheets = new List<Worksheet>();
            Styles = new List<Style>();
            Styles.Add(new Style("default"));
            Styles.Add(Style.BasicStyles.DottedFill_0_125);
            WorkbookMetadata = new Metadata();
            if (createWorkSheet == true) AddWorksheet("Sheet1");
        }

		public Workbook(string sheetName) : this(false) {
			AddWorksheet(sheetName);
		}

        public Workbook(string filename, string sheetName) : this(false) {
            FileName = filename;
            AddWorksheet(sheetName);
        }

        /// <summary>
        /// 增加工作表
        /// </summary>
        public void AddWorksheet(string name) {
            foreach(Worksheet item in Worksheets) {
                if (item.SheetName == name)
                    throw new WorksheetNameAlreadxExistsException("已存在同名工作表[" + name + "].");
            }
            int number = Worksheets.Count + 1;
            CurrentWorksheet = new Worksheet(name, number);
            Worksheets.Add(CurrentWorksheet);
        }

        /// <summary>
        /// 设置当前工作表
        /// </summary>
        /// <param name="name">工作表名称</param>
        /// <returns>返回设置的工作表</returns>
        public Worksheet SetCurrentWorksheet(string name) {
            bool exists = false;
            foreach (Worksheet item in Worksheets) {
                if (item.SheetName == name) {
                    CurrentWorksheet = item;
                    exists = true;
                    break;
                }
            }
            if (exists == false)
                throw new UnknownWorksheetException("工作表[" + name + "]不存在.");
            return CurrentWorksheet;
        }

        public void SetSelectedWorksheet(int worksheetIndex) {
            if (worksheetIndex < 0 || worksheetIndex > Worksheets.Count - 1) {
                throw new OutOfRangeException("工作表索引 " + worksheetIndex.ToString() + " 超出范围.");
            }
            SelectedWorksheet = worksheetIndex;
        }
        
        public void SetSelectedWorksheet(Worksheet worksheet) {
        	bool check = false;
            for(int i = 0, n = Worksheets.Count; i < n; i++) {
                if (Worksheets[i].Equals(worksheet)) {
                    SelectedWorksheet = i;
                    check = true;
                    break;
                }
            }
            if (check == false)
            	throw new UnknownWorksheetException("工作表不在本工作薄中.");
        }

        public void RemoveWorksheet(string name) {
            bool exists = false;
            bool resetCurrent = false;
            int index = 0;
			var ws = Worksheets;
            for (int i = 0, n = ws.Count; i < n; i++) {
                if (ws[i].SheetName == name) {
                    index = i;
                    exists = true;
                    break;
                }
            }
            if (exists == false)
                throw new UnknownWorksheetException("工作表[" + name + "]不存在.");
            if (ws[index].SheetName == CurrentWorksheet.SheetName )
                resetCurrent = true;
            ws.RemoveAt(index);
            if (ws.Count > 0) {
                for (int i = 0, n = ws.Count; i < n; i++) {
                    ws[i].SheetID = i + 1;
                    if (resetCurrent == true && i == 0)
                        CurrentWorksheet = ws[i];
                }
            }
            else CurrentWorksheet = null;
            if (SelectedWorksheet > ws.Count - 1)
                SelectedWorksheet = ws.Count - 1;
        }

        public void SetWorkbookProtection(bool state, bool protectWindows, bool protectStructure, string password) {
            LockWindowsIfProtected = protectWindows;
            LockStructureIfProtected = protectStructure;
            WorkbookProtectionPassword = password;
            if (protectWindows == false && protectStructure == false)
                UseWorkbookProtection = false;
            else
                UseWorkbookProtection = state;
        }

        /// <summary>
        /// 增加样式
        /// </summary>
        /// <param name="style">需要增加的样式</param>
        /// <param name="distinct">如果为真，则新样式将覆盖已有同样式的identical. 否则，发现同样样式时抛出异常</param>
        /// <returns>返回增加的样式</returns>
        public Style AddStyle(Style style, bool distinct) {
            bool styleExits = false;
            bool identicalStyle = false;
			var ss = Styles;
            Style s;
            for (int i = 0, n = ss.Count; i < n; i++) {
                if (ss[i].Name == style.Name) {
                    if (ss[i].Equals(style) && distinct == true) {
                        identicalStyle = true;
                        s = ss[i];
                    }
                    styleExits = true;
                    break;
                }
            }
            if (styleExits == true) {
                if (distinct == false && identicalStyle == false)
                    throw new UndefinedStyleException("The style with the name '" + style.Name + "' already exits");
                else s = style;
            }
            else {
                s = style;
                ss.Add(style);
            }
            return s;
        }

        /// <summary>
        /// 删除样式
        /// </summary>
        /// <param name="style">要删除的样式</param>
        public void RemoveStyle(Style style) {
            RemoveStyle(style, false);
        }

        public void RemoveStyle(string styleName) {
            RemoveStyle(styleName, false);
        }

        /// <summary>
        /// 删除在工作表中定义的样式
        /// </summary>
        /// <param name="style">要删除的样式</param>
        /// <param name="onlyIfUnused">如果为真，则只当未被使用时才会被删除</param>
        public void RemoveStyle(Style style, bool onlyIfUnused) {
            if (style == null)
                throw new UndefinedStyleException("The style to remove is not defined");
            RemoveStyle(style.Name, false);
        }

        public void RemoveStyle(string styleName, bool onlyIfUnused) {
            if (string.IsNullOrEmpty(styleName))
                throw new UndefinedStyleException("The style to remove is not defined (no name specified)");
            int index = -1;
			var ss = Styles;
            for(int i = 0, n = ss.Count; i < n; i++) {
                if (ss[i].Name == styleName) {
                    index = i;
                    break;
                }
            }
            if (index < 0)
                throw new UndefinedStyleException("The style with the name '" + styleName + "' to remove was not found in the list of styles");
            else if (ss[index].Name == "default" || index == 0)
                throw new UndefinedStyleException("The default style can not be removed");
            else {
                if (onlyIfUnused == true) {
                    bool styleInUse = false;
                    foreach(Worksheet sheet in Worksheets) {
                        foreach(KeyValuePair<string,Cell> cell in sheet.Cells) {
                            if (cell.Value.CellStyle.Name == styleName) {
                                styleInUse = true;
                                break;
                            }
                        }
                        if (styleInUse == true) break;
                    }
                    if (styleInUse == false) ss.RemoveAt(index);
                }
                else ss.RemoveAt(index);
            }
        }

        /// <summary>
        /// 在保存工作薄前重组样式表
        /// </summary>
        public void ReorganizeStyles(out  List<Style.Border> borders, 
				out List<Style.Fill> fills, out List<Style.Font> fonts,
				out List<Style.NumberFormat> numberFormats, 
				out List<Style.CellXf> cellXfs) {

            List<Style.Border> tempBorders = new List<Style.Border>();
            List<Style.Fill> tempFills = new List<Style.Fill>();
            List<Style.Font> tempFonts = new List<Style.Font>();
            List<Style.NumberFormat> tempNumberFormats = new List<Style.NumberFormat>();
            List<Style.CellXf> tempCellXfs = new List<Style.CellXf>();
            Style dateStyle = AddStyle(Style.BasicStyles.DateFormat, true);
            int existingIndex = 0;
            bool existing;
            int customNumberFormat = Style.NumberFormat.CUSTOMFORMAT_START_NUMBER;
			var ss = Styles;
            for(int i = 0, n = ss.Count; i < n; i++) {
				var cs = ss[i];
                cs.InternalID = i;
                existing = false;
                foreach(Style.Border item in tempBorders) {
                    if (item.Equals(cs.CurrentBorder) == true) { 
                        existing = true;
                        existingIndex = item.InternalID;
                        break;
                    }
                }
                if (existing == false) {
                    cs.CurrentBorder.InternalID = tempBorders.Count;
                    tempBorders.Add(cs.CurrentBorder);
                }
                else {
                    cs.CurrentBorder.InternalID = existingIndex;
                }
                existing = false;
                foreach (Style.Fill item in tempFills) {
                    if (item.Equals(cs.CurrentFill) == true) {
                        existing = true;
                        existingIndex = item.InternalID;
                        break;
                    }
                }
                if (existing == false) {
                    cs.CurrentFill.InternalID = tempFills.Count;
                    tempFills.Add(cs.CurrentFill);
                }
                else {
                    cs.CurrentFill.InternalID = existingIndex;
                }
                existing = false;
                foreach (Style.Font item in tempFonts) {
                    if (item.Equals(cs.CurrentFont) == true) {
                        existing = true;
                        existingIndex = item.InternalID;
                        break;
                    }
                }
                if (existing == false) {
                    cs.CurrentFont.InternalID = tempFonts.Count;
                    tempFonts.Add(cs.CurrentFont);
                }
                else {
                    cs.CurrentFont.InternalID = existingIndex;
                }
                existing = false;
                foreach (Style.NumberFormat item in tempNumberFormats) {
                    if (item.Equals(cs.CurrentNumberFormat) == true) {
                        existing = true;
                        existingIndex = item.InternalID;
                        break;
                    }
                }
                if (existing == false) {
                    cs.CurrentNumberFormat.InternalID = tempNumberFormats.Count;
                    tempNumberFormats.Add(cs.CurrentNumberFormat);
                }
                else {
                    cs.CurrentNumberFormat.InternalID = existingIndex;
                }
                if (cs.CurrentNumberFormat.IsCustomFormat == true) {
                    cs.CurrentNumberFormat.CustomFormatID = customNumberFormat;
                    customNumberFormat++;
                }
                existing = false;
                foreach (Style.CellXf item in tempCellXfs) {
                    if (item.Equals(cs.CurrentCellXf) == true) {
                        existing = true;
                        existingIndex = item.InternalID;
                        break;
                    }
                }
                if (existing == false) {
                    cs.CurrentCellXf.InternalID = tempCellXfs.Count;
                    tempCellXfs.Add(cs.CurrentCellXf);
                }
                else {
                    cs.CurrentCellXf.InternalID = existingIndex;
                }
            }
            Style combiation;
            foreach(Worksheet sheet in Worksheets) {
                foreach(KeyValuePair<string, Cell> cell in sheet.Cells) {
                    if (cell.Value.Fieldtype == Cell.CellType.DATE) {
                        if (cell.Value.CellStyle == null) {
                            combiation = dateStyle;
							sheet.Cells[cell.Key].SetStyle(combiation, this);
                        }
                        //else
                        //{
                        //    combiation = cell.Value.CellStyle.Copy(dateStyle.CurrentNumberFormat);
                        //}
                    }
                }
            }

            Styles.Sort();
            tempBorders.Sort();
            tempFills.Sort();
            tempFonts.Sort();
            tempNumberFormats.Sort();
            tempCellXfs.Sort();
            borders = tempBorders;
            fonts = tempFonts;
            fills = tempFills;
            cellXfs = tempCellXfs;
            numberFormats = tempNumberFormats;

        }

        /// <summary>
        /// 解决合并单元格的不连续状态
        /// </summary>
        public void ResolveMergedCells() {
            int pos;
            List<Cell.Address> addresses;
            Cell cell;
            foreach(Worksheet sheet in Worksheets) {
                foreach(KeyValuePair<string, Cell.Range> range in sheet.MergedCells) {
                    pos = 0;
                    addresses = Cell.GetCellRange(range.Value.StartAddress, range.Value.EndAddress);
                    foreach(Cell.Address address in addresses) {
                        if (!sheet.Cells.ContainsKey(address.ToString())) {
                            cell = new Cell();
                            cell.Fieldtype = Cell.CellType.EMPTY;
                            cell.RowAddress = address.RowIndex;
                            cell.ColumnAddress = address.ColumnIndex;
                            sheet.AddCell(cell);
                        }
                        else
                            cell = sheet.Cells[address.ToString()];
                        if (pos != 0) cell.Fieldtype = Cell.CellType.EMPTY;
                        pos++;
                    }
                }
            }
        }

		public void Write(Stream stream) {
			LowLevel l = new LowLevel(this);
			l.Save(stream);
		}
        
        public void Save() {
            LowLevel l = new LowLevel(this);
            l.Save();
        }

        public void SaveAs(string filename) {
            string backup = FileName;
            FileName = filename;
            LowLevel l = new LowLevel(this);
            l.Save();
            FileName = backup;
        }

    }
}
